package util;

import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class SM4Utils {
    // 密钥字符串，初始为空
    public static String secretKey = "";
    // 初始化向量，固定值为"UISwD9fW6cFh9SNS"
    public static String iv = "UISwD9fW6cFh9SNS";
    // 布尔值表示密钥是否为十六进制字符串，默认为 false
    public static boolean hexString = false;
    // 字符编码名称，默认为"GBK"
    public static String encInfo = "GBK";

    /**
     * 通过密钥和具体数据进行ASCII字典升序排序进行md5加密,加密之后再转为16进制字符串
     *
     * @param MD5Key SM4加密后的Md5Key
     * @return 验签
     */
    public static String getSignMd5(String MD5Key, Map<String, String> map) {
        String sortedString = map.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(Collectors.joining("&"));
        return MD5Utils.md5Digest(sortedString + "&key=" + MD5Key);
    }

    /**
     * 加密
     *
     * @param secretkey 密钥
     */
    public static String encryptData(String secretkey, String data) {
        //加密
        return encryptData_CBC(iv, secretkey, data, hexString, encInfo);
    }
    /**
     * 加密自定义编码
     *
     * @param secretkey 密钥
     */
    public static String encryptData(String secretkey, String data, String encInfo) {
        //加密
        return encryptData_CBC(iv, secretkey, data, hexString, encInfo);
    }

    /**
     * 解密
     *
     * @param secretkey
     * @param data       加密数据
     * @return 解密数据
     * @throws Exception
     */
    public static String decryptData(String secretkey, String data) {
        return decryptData_CBC(iv, secretkey, data, hexString, encInfo);
    }

    /**
     * 解密自定义编码
     *
     * @param secretkey 密钥
     * @param data       加密数据
     * @return 解密数据
     * @throws Exception
     */
    public static String decryptData(String secretkey, String data, String encInfo) {
        return decryptData_CBC(iv, secretkey, data, hexString, encInfo);
    }

    /**
     * 使用 ECB 模式加密数据的方法
     *
     * @param secretKey 密钥
     * @param data      数据
     * @param hexString 表示密钥是否为十六进制字符串
     * @param encInfo   字符编码名称
     * @return 加密后数据
     */
    public static String encryptData_ECB(String secretKey, String data, boolean hexString, String encInfo) {
        try {
            // 创建 SM4 上下文对象
            SM4_Context ctx = new SM4_Context();
            // 设置填充模式为开启
            ctx.isPadding = true;
            // 设置加密模式为 1（ECB 加密模式）
            ctx.mode = 1;
            byte[] keyBytes;
            // 如果 hexString 为 true，表示密钥是十六进制字符串，进行转换
            if (hexString) {
                keyBytes = hexStringToBytes(secretKey);
            } else {
                // 否则直接将密钥字符串转换为字节数组
                keyBytes = secretKey.getBytes();
            }
            // 创建 SM4 加密对象
            SM4 sm4 = new SM4();
            // 设置加密密钥
            sm4.sm4_setkey_enc(ctx, keyBytes);
            // 使用 ECB 模式加密数据
            byte[] encrypted = sm4.sm4_crypt_ecb(ctx, data.getBytes(encInfo));
            // 对加密后的字节数组进行 Base64 编码
            String cipherText = (new BASE64Encoder()).encode(encrypted);
            if (cipherText != null && cipherText.trim().length() > 0) {
                // 使用正则表达式去除加密结果中的空白字符（空格、制表符、回车、换行）
                Pattern p = Pattern.compile("\\s*|\t|\r|\n");
                Matcher m = p.matcher(cipherText);
                cipherText = m.replaceAll("");
            }
            // 返回加密后的字符串
            return cipherText;
        } catch (Exception var9) {
            // 打印异常信息
            var9.printStackTrace();
            return null;
        }
    }

    /**
     * 使用 ECB 模式解密数据的方法
     *
     * @param secretKey 密钥
     * @param data      数据
     * @param hexString 表示密钥是否为十六进制字符串
     * @param encInfo   字符编码名称
     * @return 解密后数据
     */
    public static String decryptData_ECB(String secretKey, String data, boolean hexString, String encInfo) {
        try {
            // 创建 SM4 上下文对象
            SM4_Context ctx = new SM4_Context();
            // 设置填充模式为开启
            ctx.isPadding = true;
            // 设置解密模式为 0（ECB 解密模式）
            ctx.mode = 0;
            byte[] keyBytes;
            // 如果 hexString 为 true，表示密钥是十六进制字符串，进行转换
            if (hexString) {
                keyBytes = hexStringToBytes(secretKey);
            } else {
                // 否则直接将密钥字符串转换为字节数组
                keyBytes = secretKey.getBytes();
            }
            // 创建 SM4 加密对象
            SM4 sm4 = new SM4();
            // 设置解密密钥
            sm4.sm4_setkey_dec(ctx, keyBytes);
            // 使用 ECB 模式解密数据，先进行 Base64 解码
            byte[] decrypted = sm4.sm4_crypt_ecb(ctx, (new BASE64Decoder()).decodeBuffer(data));
            // 使用指定的字符编码将解密后的字节数组转换为字符串
            return new String(decrypted, encInfo);
        } catch (Exception var6) {
            // 打印异常信息
            var6.printStackTrace();
            return null;
        }
    }

    /**
     * 使用 CBC 模式加密数据的方法
     *
     * @param iv        初始化向量
     * @param secretKey 密钥
     * @param data      数据
     * @param hexString 表示密钥是否为十六进制字符串
     * @param encInfo   字符编码名称
     * @return 加密后数据
     */
    public static String encryptData_CBC(String iv, String secretKey, String data, boolean hexString, String encInfo) {
        // 创建 Base64 编码对象
        Base64 base64 = new Base64();

        try {
            // 创建 SM4 上下文对象
            SM4_Context ctx = new SM4_Context();
            // 设置填充模式为开启
            ctx.isPadding = true;
            // 设置加密模式为 1（CBC 加密模式）
            ctx.mode = 1;
            byte[] keyBytes;
            byte[] ivBytes;
            // 如果 hexString 为 true，表示密钥和初始化向量是十六进制字符串，进行转换
            if (hexString) {
                keyBytes = hexStringToBytes(secretKey);
                ivBytes = hexStringToBytes(iv);
            } else {
                // 否则直接将密钥字符串和初始化向量字符串转换为字节数组
                keyBytes = secretKey.getBytes();
                ivBytes = iv.getBytes();
            }
            // 创建 SM4 加密对象
            SM4 sm4 = new SM4();
            // 设置加密密钥
            sm4.sm4_setkey_enc(ctx, keyBytes);
            // 使用 CBC 模式加密数据，传入初始化向量和待加密的字节数组
            byte[] encrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, data.getBytes(encInfo));
            // 对加密后的字节数组进行 Base64 编码
            String cipherText = (new BASE64Encoder()).encode(encrypted);
            if (cipherText != null && cipherText.trim().length() > 0) {
                // 使用正则表达式去除加密结果中的空白字符（空格、制表符、回车、换行）
                Pattern p = Pattern.compile("\\s*|\t|\r|\n");
                Matcher m = p.matcher(cipherText);
                cipherText = m.replaceAll("");
                // 将去除空白字符后的字符串进行十六进制转换
                cipherText = byteToHex(base64.decode(cipherText));
            }
            // 返回加密后的字符串
            return cipherText;
        } catch (Exception var11) {
            // 打印异常信息
            var11.printStackTrace();
            return null;
        }
    }

    /**
     * 使用 CBC 模式解密数据的方法
     *
     * @param iv        初始化向量
     * @param secretKey 密钥
     * @param data      数据
     * @param hexString 表示密钥是否为十六进制字符串
     * @param encInfo   字符编码名称
     * @return 解密后数据
     */
    public static String decryptData_CBC(String iv, String secretKey, String data, boolean hexString, String encInfo) {
        try {
            // 创建 SM4 上下文对象
            SM4_Context ctx = new SM4_Context();
            // 设置填充模式为开启
            ctx.isPadding = true;
            // 设置解密模式为 0（CBC 解密模式）
            ctx.mode = 0;
            byte[] keyBytes;
            byte[] ivBytes;
            // 如果 hexString 为 true，表示密钥和初始化向量是十六进制字符串，进行转换
            if (hexString) {
                keyBytes = hexStringToBytes(secretKey);
                ivBytes = hexStringToBytes(iv);
            } else {
                // 否则直接将密钥字符串和初始化向量字符串转换为字节数组
                keyBytes = secretKey.getBytes();
                ivBytes = iv.getBytes();
            }
            // 创建 Base64 编码对象
            Base64 base64 = new Base64();
            // 将十六进制字符串转换后的密文进行 Base64 编码
            data = base64.encodeToString(hexToByte(data));
            // 创建 SM4 加密对象
            SM4 sm4 = new SM4();
            // 设置解密密钥
            sm4.sm4_setkey_dec(ctx, keyBytes);
            // 使用 CBC 模式解密数据，传入初始化向量和经过 Base64 解码后的密文字节数组
            byte[] decrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, (new BASE64Decoder()).decodeBuffer(data));
            // 使用指定的字符编码将解密后的字节数组转换为字符串
            return new String(decrypted, encInfo);
        } catch (Exception var8) {
            // 打印异常信息
            var8.printStackTrace();
            return null;
        }
    }

    // 将十六进制字符串转换为字节数组
    public static byte[] hexStringToBytes(String hexString) {
        if (hexString != null && !hexString.equals("")) {
            hexString = hexString.toUpperCase();
            int length = hexString.length() / 2;
            char[] hexChars = hexString.toCharArray();
            byte[] d = new byte[length];

            for (int i = 0; i < length; ++i) {
                int pos = i * 2;
                d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
            }

            return d;
        } else {
            return null;
        }
    }

    // 将十六进制字符转换为字节
    public static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    // 将十六进制字符串转换为字节数组，若格式错误则抛出异常
    public static byte[] hexToByte(String hex) throws IllegalArgumentException {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException();
        } else {
            char[] arr = hex.toCharArray();
            byte[] b = new byte[hex.length() / 2];
            int i = 0;
            int j = 0;

            for (int l = hex.length(); i < l; ++j) {
                String swap = "" + arr[i++] + arr[i];
                int byteint = Integer.parseInt(swap, 16) & 255;
                b[j] = (new Integer(byteint)).byteValue();
                ++i;
            }

            return b;
        }
    }

    // 将字节数组转换为十六进制字符串
    public static String byteToHex(byte[] b) {
        if (b == null) {
            throw new IllegalArgumentException("Argument b ( byte array ) is null! ");
        } else {
            String hs = "";
            String stmp = "";

            for (int n = 0; n < b.length; ++n) {
                stmp = Integer.toHexString(b[n] & 255);
                if (stmp.length() == 1) {
                    hs = hs + "0" + stmp;
                } else {
                    hs = hs + stmp;
                }
            }

            return hs;
        }
    }

    /**
     * 将字节转成十六进制字符串
     * @param p_bytes
     * @return
     */
    public static String bytesToHexString(byte[] p_bytes){
        if (p_bytes == null || p_bytes.length <= 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < p_bytes.length; i++) {
            int each = p_bytes[i] & 0xFF;
            String eachHex = Integer.toHexString(each);
            if (eachHex.length() < 2) {
                sb.append(0);
            }
            sb.append(eachHex);
        }
        return sb.toString();
    }
}
